1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.solr.analytics.statistics;
19
20 import java.lang.invoke.MethodHandles;
21 import java.text.ParseException;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.TreeMap;
30
31 import org.apache.lucene.queries.function.ValueSource;
32 import org.apache.lucene.queries.function.valuesource.BytesRefFieldSource;
33 import org.apache.lucene.queries.function.valuesource.DoubleFieldSource;
34 import org.apache.lucene.queries.function.valuesource.FloatFieldSource;
35 import org.apache.lucene.queries.function.valuesource.IntFieldSource;
36 import org.apache.lucene.queries.function.valuesource.LongFieldSource;
37 import org.apache.solr.analytics.expression.ExpressionFactory;
38 import org.apache.solr.analytics.request.ExpressionRequest;
39 import org.apache.solr.analytics.util.AnalyticsParams;
40 import org.apache.solr.analytics.util.AnalyticsParsers;
41 import org.apache.solr.analytics.util.valuesource.AbsoluteValueDoubleFunction;
42 import org.apache.solr.analytics.util.valuesource.AddDoubleFunction;
43 import org.apache.solr.analytics.util.valuesource.ConcatStringFunction;
44 import org.apache.solr.analytics.util.valuesource.ConstDateSource;
45 import org.apache.solr.analytics.util.valuesource.ConstDoubleSource;
46 import org.apache.solr.analytics.util.valuesource.ConstStringSource;
47 import org.apache.solr.analytics.util.valuesource.DateFieldSource;
48 import org.apache.solr.analytics.util.valuesource.DateMathFunction;
49 import org.apache.solr.analytics.util.valuesource.DivDoubleFunction;
50 import org.apache.solr.analytics.util.valuesource.DualDoubleFunction;
51 import org.apache.solr.analytics.util.valuesource.FilterFieldSource;
52 import org.apache.solr.analytics.util.valuesource.LogDoubleFunction;
53 import org.apache.solr.analytics.util.valuesource.MultiDateFunction;
54 import org.apache.solr.analytics.util.valuesource.MultiDoubleFunction;
55 import org.apache.solr.analytics.util.valuesource.MultiplyDoubleFunction;
56 import org.apache.solr.analytics.util.valuesource.NegateDoubleFunction;
57 import org.apache.solr.analytics.util.valuesource.PowDoubleFunction;
58 import org.apache.solr.analytics.util.valuesource.ReverseStringFunction;
59 import org.apache.solr.analytics.util.valuesource.SingleDoubleFunction;
60 import org.apache.solr.common.SolrException;
61 import org.apache.solr.common.SolrException.ErrorCode;
62 import org.apache.solr.schema.FieldType;
63 import org.apache.solr.schema.IndexSchema;
64 import org.apache.solr.schema.SchemaField;
65 import org.apache.solr.schema.StrField;
66 import org.apache.solr.schema.TrieDateField;
67 import org.apache.solr.schema.TrieDoubleField;
68 import org.apache.solr.schema.TrieFloatField;
69 import org.apache.solr.schema.TrieIntField;
70 import org.apache.solr.schema.TrieLongField;
71 import org.apache.solr.util.DateFormatUtil;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 import com.google.common.base.Supplier;
76
77 public class StatsCollectorSupplierFactory {
78 private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
79
80
81 final static int NUMBER_TYPE = 0;
82 final static int DATE_TYPE = 1;
83 final static int STRING_TYPE = 2;
84 final static int FIELD_TYPE = 3;
85 final static int FILTER_TYPE = 4;
86
87
88
89
90
91
92
93
94 @SuppressWarnings("unchecked")
95 public static Supplier<StatsCollector[]> create(IndexSchema schema, List<ExpressionRequest> exRequests ) {
96 final Map<String, Set<String>> collectorStats = new TreeMap<>();
97 final Map<String, Set<Integer>> collectorPercs = new TreeMap<>();
98 final Map<String, ValueSource> collectorSources = new TreeMap<>();
99
100
101
102 for (ExpressionRequest expRequest : exRequests) {
103 String statExpression = expRequest.getExpressionString();
104 Set<String> statistics = getStatistics(statExpression);
105 if (statistics == null) {
106 continue;
107 }
108 for (String statExp : statistics) {
109 String stat;
110 String operands;
111 try {
112 stat = statExp.substring(0, statExp.indexOf('(')).trim();
113 operands = statExp.substring(statExp.indexOf('(')+1, statExp.lastIndexOf(')')).trim();
114 } catch (Exception e) {
115 throw new SolrException(ErrorCode.BAD_REQUEST,"Unable to parse statistic: ["+statExpression+"]",e);
116 }
117 String[] arguments = ExpressionFactory.getArguments(operands);
118 String source = arguments[0];
119 if (stat.equals(AnalyticsParams.STAT_PERCENTILE)) {
120
121 if (arguments.length<2) {
122 throw new SolrException(ErrorCode.BAD_REQUEST,"Too few arguments given for "+stat+"() in ["+statExp+"].");
123 } else if (arguments.length>2) {
124 throw new SolrException(ErrorCode.BAD_REQUEST,"Too many arguments given for "+stat+"() in ["+statExp+"].");
125 }
126 source = arguments[1];
127 Set<Integer> percs = collectorPercs.get(source);
128 if (percs == null) {
129 percs = new HashSet<>();
130 collectorPercs.put(source, percs);
131 }
132 try {
133 int perc = Integer.parseInt(arguments[0]);
134 if (perc>0 && perc<100) {
135 percs.add(perc);
136 } else {
137 throw new SolrException(ErrorCode.BAD_REQUEST,"The percentile in ["+statExp+"] is not between 0 and 100, exculsive.");
138 }
139 } catch (NumberFormatException e) {
140 throw new SolrException(ErrorCode.BAD_REQUEST,"\""+arguments[0]+"\" cannot be converted into a percentile.",e);
141 }
142 } else if (arguments.length>1) {
143 throw new SolrException(ErrorCode.BAD_REQUEST,"Too many arguments given for "+stat+"() in ["+statExp+"].");
144 } else if (arguments.length==0) {
145 throw new SolrException(ErrorCode.BAD_REQUEST,"No arguments given for "+stat+"() in ["+statExp+"].");
146 }
147
148
149 Set<String> stats = collectorStats.get(source);
150 if (stats == null) {
151 stats = new HashSet<>();
152 collectorStats.put(source, stats);
153 }
154 if(AnalyticsParams.STAT_PERCENTILE.equals(stat)) {
155 stats.add(stat + "_"+ arguments[0]);
156 } else {
157 stats.add(stat);
158 }
159 }
160 }
161 String[] keys = collectorStats.keySet().toArray(new String[0]);
162 for (String sourceStr : keys) {
163
164 ValueSource source = buildSourceTree(schema, sourceStr);
165 if (source == null) {
166 throw new SolrException(ErrorCode.BAD_REQUEST,"The statistic ["+sourceStr+"] could not be parsed.");
167 }
168 String builtString = source.toString();
169 collectorSources.put(builtString,source);
170
171 if (!builtString.equals(sourceStr)) {
172 Set<String> stats = collectorStats.remove(sourceStr);
173 if (stats!=null) {
174 collectorStats.put(builtString, stats);
175 }
176 Set<Integer> percs = collectorPercs.remove(sourceStr);
177 if (percs!=null) {
178 collectorPercs.put(builtString, percs);
179 }
180 for (ExpressionRequest er : exRequests) {
181 er.setExpressionString(er.getExpressionString().replace(sourceStr, builtString));
182 }
183 }
184 }
185 if (collectorSources.size()==0) {
186 return new Supplier<StatsCollector[]>() {
187 @Override
188 public StatsCollector[] get() {
189 return new StatsCollector[0];
190 }
191 };
192 }
193
194 log.info("Stats objects: "+collectorStats.size()+" sr="+collectorSources.size()+" pr="+collectorPercs.size() );
195
196
197
198 final Set<String>[] statsArr = collectorStats.values().toArray(new Set[0]);
199 final ValueSource[] sourceArr = collectorSources.values().toArray(new ValueSource[0]);
200 final boolean[] uniqueBools = new boolean[statsArr.length];
201 final boolean[] medianBools = new boolean[statsArr.length];
202 final boolean[] numericBools = new boolean[statsArr.length];
203 final boolean[] dateBools = new boolean[statsArr.length];
204 final double[][] percsArr = new double[statsArr.length][];
205 final String[][] percsNames = new String[statsArr.length][];
206 for (int count = 0; count < sourceArr.length; count++) {
207 uniqueBools[count] = statsArr[count].contains(AnalyticsParams.STAT_UNIQUE);
208 medianBools[count] = statsArr[count].contains(AnalyticsParams.STAT_MEDIAN);
209 numericBools[count] = statsArr[count].contains(AnalyticsParams.STAT_SUM)||statsArr[count].contains(AnalyticsParams.STAT_SUM_OF_SQUARES)||statsArr[count].contains(AnalyticsParams.STAT_MEAN)||statsArr[count].contains(AnalyticsParams.STAT_STANDARD_DEVIATION);
210 dateBools[count] = (sourceArr[count] instanceof DateFieldSource) | (sourceArr[count] instanceof MultiDateFunction) | (sourceArr[count] instanceof ConstDateSource);
211 Set<Integer> ps = collectorPercs.get(sourceArr[count].toString());
212 if (ps!=null) {
213 percsArr[count] = new double[ps.size()];
214 percsNames[count] = new String[ps.size()];
215 int percCount = 0;
216 for (int p : ps) {
217 percsArr[count][percCount] = p/100.0;
218 percsNames[count][percCount++] = AnalyticsParams.STAT_PERCENTILE+"_"+p;
219 }
220 }
221 }
222
223 return new Supplier<StatsCollector[]>() {
224 public StatsCollector[] get() {
225 StatsCollector[] collectors = new StatsCollector[statsArr.length];
226 for (int count = 0; count < statsArr.length; count++) {
227 if(numericBools[count]){
228 StatsCollector sc = new NumericStatsCollector(sourceArr[count], statsArr[count]);
229 if(uniqueBools[count]) sc = new UniqueStatsCollector(sc);
230 if(medianBools[count]) sc = new MedianStatsCollector(sc);
231 if(percsArr[count]!=null) sc = new PercentileStatsCollector(sc,percsArr[count],percsNames[count]);
232 collectors[count]=sc;
233 } else if (dateBools[count]) {
234 StatsCollector sc = new MinMaxStatsCollector(sourceArr[count], statsArr[count]);
235 if(uniqueBools[count]) sc = new UniqueStatsCollector(sc);
236 if(medianBools[count]) sc = new DateMedianStatsCollector(sc);
237 if(percsArr[count]!=null) sc = new PercentileStatsCollector(sc,percsArr[count],percsNames[count]);
238 collectors[count]=sc;
239 } else {
240 StatsCollector sc = new MinMaxStatsCollector(sourceArr[count], statsArr[count]);
241 if(uniqueBools[count]) sc = new UniqueStatsCollector(sc);
242 if(medianBools[count]) sc = new MedianStatsCollector(sc);
243 if(percsArr[count]!=null) sc = new PercentileStatsCollector(sc,percsArr[count],percsNames[count]);
244 collectors[count]=sc;
245 }
246 }
247 return collectors;
248 }
249 };
250 }
251
252
253
254
255
256
257 public static Set<String> getStatistics(String expression) {
258 HashSet<String> set = new HashSet<>();
259 int firstParen = expression.indexOf('(');
260 if (firstParen>0) {
261 String topOperation = expression.substring(0,firstParen).trim();
262 if (AnalyticsParams.ALL_STAT_SET.contains(topOperation)) {
263 set.add(expression);
264 } else if (!(topOperation.equals(AnalyticsParams.CONSTANT_NUMBER)||topOperation.equals(AnalyticsParams.CONSTANT_DATE)||topOperation.equals(AnalyticsParams.CONSTANT_STRING))) {
265 String operands = expression.substring(firstParen+1, expression.lastIndexOf(')')).trim();
266 String[] arguments = ExpressionFactory.getArguments(operands);
267 for (String argument : arguments) {
268 Set<String> more = getStatistics(argument);
269 if (more!=null) {
270 set.addAll(more);
271 }
272 }
273 }
274 }
275 if (set.size()==0) {
276 return null;
277 }
278 return set;
279 }
280
281
282
283
284
285
286
287
288 private static ValueSource buildSourceTree(IndexSchema schema, String expression) {
289 return buildSourceTree(schema,expression,FIELD_TYPE);
290 }
291
292
293
294
295
296
297
298
299
300 private static ValueSource buildSourceTree(IndexSchema schema, String expression, int sourceType) {
301 int expressionType = getSourceType(expression);
302 if (sourceType != FIELD_TYPE && expressionType != FIELD_TYPE &&
303 expressionType != FILTER_TYPE && expressionType != sourceType) {
304 return null;
305 }
306 switch (expressionType) {
307 case NUMBER_TYPE : return buildNumericSource(schema, expression);
308 case DATE_TYPE : return buildDateSource(schema, expression);
309 case STRING_TYPE : return buildStringSource(schema, expression);
310 case FIELD_TYPE : return buildFieldSource(schema, expression, sourceType);
311 case FILTER_TYPE : return buildFilterSource(schema, expression.substring(expression.indexOf('(')+1,expression.lastIndexOf(')')), sourceType);
312 default : throw new SolrException(ErrorCode.BAD_REQUEST,expression+" is not a valid operation.");
313 }
314 }
315
316
317
318
319
320
321
322 private static int getSourceType(String expression) {
323 int paren = expression.indexOf('(');
324 if (paren<0) {
325 return FIELD_TYPE;
326 }
327 String operation = expression.substring(0,paren).trim();
328
329 if (AnalyticsParams.NUMERIC_OPERATION_SET.contains(operation)) {
330 return NUMBER_TYPE;
331 } else if (AnalyticsParams.DATE_OPERATION_SET.contains(operation)) {
332 return DATE_TYPE;
333 } else if (AnalyticsParams.STRING_OPERATION_SET.contains(operation)) {
334 return STRING_TYPE;
335 } else if (operation.equals(AnalyticsParams.FILTER)) {
336 return FILTER_TYPE;
337 }
338 throw new SolrException(ErrorCode.BAD_REQUEST,"The operation \""+operation+"\" in ["+expression+"] is not supported.");
339 }
340
341
342
343
344
345
346
347
348
349 private static ValueSource buildFieldSource(IndexSchema schema, String expressionString, int sourceType) {
350 SchemaField sf;
351 try {
352 sf = schema.getField(expressionString);
353 } catch (SolrException e) {
354 throw new SolrException(ErrorCode.BAD_REQUEST,"The field "+expressionString+" does not exist.",e);
355 }
356 FieldType type = sf.getType();
357 if ( type instanceof TrieIntField) {
358 if (sourceType!=NUMBER_TYPE&&sourceType!=FIELD_TYPE) {
359 return null;
360 }
361 return new IntFieldSource(expressionString) {
362 public String description() {
363 return field;
364 }
365 };
366 } else if (type instanceof TrieLongField) {
367 if (sourceType!=NUMBER_TYPE&&sourceType!=FIELD_TYPE) {
368 return null;
369 }
370 return new LongFieldSource(expressionString) {
371 public String description() {
372 return field;
373 }
374 };
375 } else if (type instanceof TrieFloatField) {
376 if (sourceType!=NUMBER_TYPE&&sourceType!=FIELD_TYPE) {
377 return null;
378 }
379 return new FloatFieldSource(expressionString) {
380 public String description() {
381 return field;
382 }
383 };
384 } else if (type instanceof TrieDoubleField) {
385 if (sourceType!=NUMBER_TYPE&&sourceType!=FIELD_TYPE) {
386 return null;
387 }
388 return new DoubleFieldSource(expressionString) {
389 public String description() {
390 return field;
391 }
392 };
393 } else if (type instanceof TrieDateField) {
394 if (sourceType!=DATE_TYPE&&sourceType!=FIELD_TYPE) {
395 return null;
396 }
397 return new DateFieldSource(expressionString) {
398 public String description() {
399 return field;
400 }
401 };
402 } else if (type instanceof StrField) {
403 if (sourceType!=STRING_TYPE&&sourceType!=FIELD_TYPE) {
404 return null;
405 }
406 return new BytesRefFieldSource(expressionString) {
407 public String description() {
408 return field;
409 }
410 };
411 }
412 throw new SolrException(ErrorCode.BAD_REQUEST, type.toString()+" is not a supported field type in Solr Analytics.");
413 }
414
415
416
417
418
419
420
421
422
423
424 @SuppressWarnings("deprecation")
425 private static ValueSource buildFilterSource(IndexSchema schema, String expressionString, int sourceType) {
426 String[] arguments = ExpressionFactory.getArguments(expressionString);
427 if (arguments.length!=2) {
428 throw new SolrException(ErrorCode.BAD_REQUEST,"Invalid arguments were given for \""+AnalyticsParams.FILTER+"\".");
429 }
430 ValueSource delegateSource = buildSourceTree(schema, arguments[0], sourceType);
431 if (delegateSource==null) {
432 return null;
433 }
434 Object defaultObject;
435
436 ValueSource src = delegateSource;
437 if (delegateSource instanceof FilterFieldSource) {
438 src = ((FilterFieldSource)delegateSource).getRootSource();
439 }
440 if ( src instanceof IntFieldSource) {
441 try {
442 defaultObject = new Integer(arguments[1]);
443 } catch (NumberFormatException e) {
444 throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into an integer.",e);
445 }
446 } else if ( src instanceof DateFieldSource || src instanceof MultiDateFunction) {
447 try {
448 defaultObject = DateFormatUtil.parseDate(arguments[1]);
449 } catch (ParseException e) {
450 throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into a date.",e);
451 }
452 } else if ( src instanceof LongFieldSource ) {
453 try {
454 defaultObject = new Long(arguments[1]);
455 } catch (NumberFormatException e) {
456 throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into a long.",e);
457 }
458 } else if ( src instanceof FloatFieldSource ) {
459 try {
460 defaultObject = new Float(arguments[1]);
461 } catch (NumberFormatException e) {
462 throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into a float.",e);
463 }
464 } else if ( src instanceof DoubleFieldSource || src instanceof SingleDoubleFunction ||
465 src instanceof DualDoubleFunction|| src instanceof MultiDoubleFunction) {
466 try {
467 defaultObject = new Double(arguments[1]);
468 } catch (NumberFormatException e) {
469 throw new SolrException(ErrorCode.BAD_REQUEST,"The filter value "+arguments[1]+" cannot be converted into a double.",e);
470 }
471 } else {
472 defaultObject = arguments[1];
473 }
474 return new FilterFieldSource(delegateSource,defaultObject);
475 }
476
477
478
479
480
481
482
483
484 private static ValueSource buildNumericSource(IndexSchema schema, String expressionString) {
485 int paren = expressionString.indexOf('(');
486 String[] arguments;
487 String operands;
488 if (paren<0) {
489 return buildFieldSource(schema,expressionString,NUMBER_TYPE);
490 } else {
491 try {
492 operands = expressionString.substring(paren+1, expressionString.lastIndexOf(')')).trim();
493 } catch (Exception e) {
494 throw new SolrException(ErrorCode.BAD_REQUEST,"Missing closing parenthesis in ["+expressionString+"]");
495 }
496 arguments = ExpressionFactory.getArguments(operands);
497 }
498 String operation = expressionString.substring(0, paren).trim();
499 if (operation.equals(AnalyticsParams.CONSTANT_NUMBER)) {
500 if (arguments.length!=1) {
501 throw new SolrException(ErrorCode.BAD_REQUEST,"The constant number declaration ["+expressionString+"] does not have exactly 1 argument.");
502 }
503 return new ConstDoubleSource(Double.parseDouble(arguments[0]));
504 } else if (operation.equals(AnalyticsParams.NEGATE)) {
505 if (arguments.length!=1) {
506 throw new SolrException(ErrorCode.BAD_REQUEST,"The negate operation ["+expressionString+"] does not have exactly 1 argument.");
507 }
508 ValueSource argSource = buildNumericSource(schema, arguments[0]);
509 if (argSource==null) {
510 throw new SolrException(ErrorCode.BAD_REQUEST,"The operation \""+AnalyticsParams.NEGATE+"\" requires a numeric field or operation as argument. \""+arguments[0]+"\" is not a numeric field or operation.");
511 }
512 return new NegateDoubleFunction(argSource);
513 } else if (operation.equals(AnalyticsParams.ABSOLUTE_VALUE)) {
514 if (arguments.length!=1) {
515 throw new SolrException(ErrorCode.BAD_REQUEST,"The absolute value operation ["+expressionString+"] does not have exactly 1 argument.");
516 }
517 ValueSource argSource = buildNumericSource(schema, arguments[0]);
518 if (argSource==null) {
519 throw new SolrException(ErrorCode.BAD_REQUEST,"The operation \""+AnalyticsParams.NEGATE+"\" requires a numeric field or operation as argument. \""+arguments[0]+"\" is not a numeric field or operation.");
520 }
521 return new AbsoluteValueDoubleFunction(argSource);
522 } else if (operation.equals(AnalyticsParams.FILTER)) {
523 return buildFilterSource(schema, operands, NUMBER_TYPE);
524 }
525 List<ValueSource> subExpressions = new ArrayList<>();
526 for (String argument : arguments) {
527 ValueSource argSource = buildNumericSource(schema, argument);
528 if (argSource == null) {
529 throw new SolrException(ErrorCode.BAD_REQUEST,"The operation \""+operation+"\" requires numeric fields or operations as arguments. \""+argument+"\" is not a numeric field or operation.");
530 }
531 subExpressions.add(argSource);
532 }
533 if (operation.equals(AnalyticsParams.ADD)) {
534 return new AddDoubleFunction(subExpressions.toArray(new ValueSource[0]));
535 } else if (operation.equals(AnalyticsParams.MULTIPLY)) {
536 return new MultiplyDoubleFunction(subExpressions.toArray(new ValueSource[0]));
537 } else if (operation.equals(AnalyticsParams.DIVIDE)) {
538 if (subExpressions.size()!=2) {
539 throw new SolrException(ErrorCode.BAD_REQUEST,"The divide operation ["+expressionString+"] does not have exactly 2 arguments.");
540 }
541 return new DivDoubleFunction(subExpressions.get(0),subExpressions.get(1));
542 } else if (operation.equals(AnalyticsParams.POWER)) {
543 if (subExpressions.size()!=2) {
544 throw new SolrException(ErrorCode.BAD_REQUEST,"The power operation ["+expressionString+"] does not have exactly 2 arguments.");
545 }
546 return new PowDoubleFunction(subExpressions.get(0),subExpressions.get(1));
547 } else if (operation.equals(AnalyticsParams.LOG)) {
548 if (subExpressions.size()!=2) {
549 throw new SolrException(ErrorCode.BAD_REQUEST,"The log operation ["+expressionString+"] does not have exactly 2 arguments.");
550 }
551 return new LogDoubleFunction(subExpressions.get(0), subExpressions.get(1));
552 }
553 if (AnalyticsParams.DATE_OPERATION_SET.contains(operation)||AnalyticsParams.STRING_OPERATION_SET.contains(operation)) {
554 return null;
555 }
556 throw new SolrException(ErrorCode.BAD_REQUEST,"The operation ["+expressionString+"] is not supported.");
557 }
558
559
560
561
562
563
564
565
566
567 @SuppressWarnings("deprecation")
568 private static ValueSource buildDateSource(IndexSchema schema, String expressionString) {
569 int paren = expressionString.indexOf('(');
570 String[] arguments;
571 if (paren<0) {
572 return buildFieldSource(schema, expressionString, DATE_TYPE);
573 } else {
574 arguments = ExpressionFactory.getArguments(expressionString.substring(paren+1, expressionString.lastIndexOf(')')).trim());
575 }
576 String operands = arguments[0];
577 String operation = expressionString.substring(0, paren).trim();
578 if (operation.equals(AnalyticsParams.CONSTANT_DATE)) {
579 if (arguments.length!=1) {
580 throw new SolrException(ErrorCode.BAD_REQUEST,"The constant date declaration ["+expressionString+"] does not have exactly 1 argument.");
581 }
582 try {
583 return new ConstDateSource(DateFormatUtil.parseDate(operands));
584 } catch (ParseException e) {
585 throw new SolrException(ErrorCode.BAD_REQUEST,"The constant "+operands+" cannot be converted into a date.",e);
586 }
587 } else if (operation.equals(AnalyticsParams.FILTER)) {
588 return buildFilterSource(schema, operands, DATE_TYPE);
589 }
590 if (operation.equals(AnalyticsParams.DATE_MATH)) {
591 List<ValueSource> subExpressions = new ArrayList<>();
592 boolean first = true;
593 for (String argument : arguments) {
594 ValueSource argSource;
595 if (first) {
596 first = false;
597 argSource = buildDateSource(schema, argument);
598 if (argSource == null) {
599 throw new SolrException(ErrorCode.BAD_REQUEST,"\""+AnalyticsParams.DATE_MATH+"\" requires the first argument be a date operation or field. ["+argument+"] is not a date operation or field.");
600 }
601 } else {
602 argSource = buildStringSource(schema, argument);
603 if (argSource == null) {
604 throw new SolrException(ErrorCode.BAD_REQUEST,"\""+AnalyticsParams.DATE_MATH+"\" requires that all arguments except the first be string operations. ["+argument+"] is not a string operation.");
605 }
606 }
607 subExpressions.add(argSource);
608 }
609 return new DateMathFunction(subExpressions.toArray(new ValueSource[0]));
610 }
611 if (AnalyticsParams.NUMERIC_OPERATION_SET.contains(operation)||AnalyticsParams.STRING_OPERATION_SET.contains(operation)) {
612 return null;
613 }
614 throw new SolrException(ErrorCode.BAD_REQUEST,"The operation ["+expressionString+"] is not supported.");
615 }
616
617
618
619
620
621
622
623
624
625 private static ValueSource buildStringSource(IndexSchema schema, String expressionString) {
626 int paren = expressionString.indexOf('(');
627 String[] arguments;
628 if (paren<0) {
629 return buildFieldSource(schema, expressionString, FIELD_TYPE);
630 } else {
631 arguments = ExpressionFactory.getArguments(expressionString.substring(paren+1, expressionString.lastIndexOf(')')).trim());
632 }
633 String operands = arguments[0];
634 String operation = expressionString.substring(0, paren).trim();
635 if (operation.equals(AnalyticsParams.CONSTANT_STRING)) {
636 operands = expressionString.substring(paren+1, expressionString.lastIndexOf(')'));
637 return new ConstStringSource(operands);
638 } else if (operation.equals(AnalyticsParams.FILTER)) {
639 return buildFilterSource(schema,operands,FIELD_TYPE);
640 } else if (operation.equals(AnalyticsParams.REVERSE)) {
641 if (arguments.length!=1) {
642 throw new SolrException(ErrorCode.BAD_REQUEST,"\""+AnalyticsParams.REVERSE+"\" requires exactly one argument. The number of arguments in "+expressionString+" is not 1.");
643 }
644 return new ReverseStringFunction(buildStringSource(schema, operands));
645 }
646 List<ValueSource> subExpressions = new ArrayList<>();
647 for (String argument : arguments) {
648 subExpressions.add(buildSourceTree(schema, argument));
649 }
650 if (operation.equals(AnalyticsParams.CONCATENATE)) {
651 return new ConcatStringFunction(subExpressions.toArray(new ValueSource[0]));
652 }
653 if (AnalyticsParams.NUMERIC_OPERATION_SET.contains(operation)) {
654 return buildNumericSource(schema, expressionString);
655 } else if (AnalyticsParams.DATE_OPERATION_SET.contains(operation)) {
656 return buildDateSource(schema, expressionString);
657 }
658 throw new SolrException(ErrorCode.BAD_REQUEST,"The operation ["+expressionString+"] is not supported.");
659 }
660 }